//
//  Copyright (C) 1998-2007 J. Andrew McLaughlin
// 
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License as published by the Free
//  Software Foundation; either version 2 of the License, or (at your option)
//  any later version.
// 
//  This program is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
//  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
//  for more details.
//  
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write to the Free Software Foundation, Inc.,
//  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  kernelWindowShell.c
//

// This is the code that manages the 'root' window in the GUI environment.

#include "kernelWindow.h"
#include "kernelWindowComponent.h"
#include "kernelWindowStdComponentInternals.h"
#include "kernelDebug.h"
#include "kernelDisk.h"
#include "kernelError.h"
#include "kernelLoader.h"
#include "kernelLog.h"
#include "kernelMalloc.h"
#include "kernelMemory.h"
#include "kernelMisc.h"
#include "core1/kernelMultitasker.h"
#include "kernelParameters.h"
#include "kernelWindowEventStream.h"
#include <string.h>
#include <stdio.h>

typedef struct {
  kernelWindowComponent *itemComponent;
  char command[MAX_PATH_NAME_LENGTH];
  kernelWindow *window;
  void (*eventHandler)(kernelWindowComponent *, windowEvent *);
} menuItem;

static kernelWindow *rootWindow = NULL;
static kernelWindowComponent *taskMenuBar = NULL;
static kernelWindowComponent *windowMenu = NULL;
static menuItem *menuItems = NULL;
static int numMenuItems = 0;
static menuItem *winMenuItems = NULL;
static int numWinMenuItems = 0;
static menuItem *iconItems = NULL;
static int numIconItems = 0;
static int privilege = 0;
static int login = 0;
static int initialized = 0;

extern kernelWindowVariables *windowVariables;


static void iconEvent(kernelWindowComponent *component, windowEvent *event)
{
  int status = 0;
  int count;
  menuItem *item = NULL;
  static int dragging = 0;
  int exec = 0;

  if (event->type & EVENT_MOUSE_DRAG)
    dragging = 1;

  else if (event->type & EVENT_MOUSE_LEFTUP)
    {
      if (dragging)
	{
	  // Drag is finished
	  dragging = 0;
	  return;
	}

      exec = 1;
      kernelDebug(debug_gui, "Icon mouse click");
    }
  else if (event->type == EVENT_KEY_DOWN && event->key == 10)
  {
	exec = 1;
  }

  if (exec)
  {
      for (count = 0; count < numIconItems; count ++)
	{
	  item = &iconItems[count];
	  if (component == item->itemComponent)
	    {
	      break;
	    }
	}

      if (!item)
          return;

      kernelWindowSwitchPointer(rootWindow, "busy");

      // Run the command
      status =
	kernelLoaderLoadAndExec((const char *) item->command,
				privilege, 0 /* no block */);

      kernelWindowSwitchPointer(rootWindow, "default");

      if (status < 0)
	kernelError("Unable to execute program %s",
		    item->command);
  }

  return;
}


static void menuEvent(kernelWindowComponent *component, windowEvent *event)
{
  int status = 0;
  int count;

  kernelDebug(debug_gui, "Taskbar menu event");

  if (event->type & EVENT_SELECTION)
    {
      kernelDebug(debug_gui, "Taskbar menu selection");

      for (count = 0; count < numMenuItems; count ++)
	{
	  if (component == menuItems[count].itemComponent)
	    {
	      if (menuItems[count].command[0] != NULL)
		{
		  kernelWindowSwitchPointer(rootWindow, "busy");

		  // Run the command, no block
		  status = kernelLoaderLoadAndExec(menuItems[count].command,
						   privilege, 0);

		  kernelWindowSwitchPointer(rootWindow, "default");

		  if (status < 0)
		    kernelError("Unable to execute program %s",
				menuItems[count].command);
		}

	      break;
	    }
	}
    }

  return;
}


static void windowMenuEvent(kernelWindowComponent *component,
			    windowEvent *event)
{
  int count;

  kernelDebug(debug_gui, "Taskbar window menu event");

  if (windowMenu && (event->type & EVENT_SELECTION))
    {
      kernelDebug(debug_gui, "Taskbar window menu selection");

      // See if this is one of our window menu components
      for (count = 0; count < numWinMenuItems; count ++)
	{
	  if (component == winMenuItems[count].itemComponent)
	    {
	      // Restore it
	      kernelWindowSetMinimized(winMenuItems[count].window, 0);
	      break;
	    }
	}
    }

  return;
}


static void runPrograms(void)
{
  // Get any programs we're supposed to run automatically and run them.

  variableList settings;
  char programName[MAX_PATH_NAME_LENGTH];
  int count;

  // Read the config file
  if (kernelConfigRead(WINDOW_DEFAULT_DESKTOP_CONFIG, &settings) < 0)
    return;

  // Loop for variables with "program.*"
  for (count = 0; count < settings.numVariables; count ++)
    {
      if (!strncmp(settings.variables[count], "program.", 8))
	{
	  if (!kernelVariableListGet(&settings, settings.variables[count],
				     programName, MAX_PATH_NAME_LENGTH))
	    // Try to run the program
	    kernelLoaderLoadAndExec(programName, privilege, 0);
	}
    }

  kernelVariableListDestroy(&settings);
  return;
}


static void scanMenuItemEvents(menuItem *items, int numItems)
{
  // Scan through events in a list of our menu items 

  kernelWindowComponent *component = NULL;  
  windowEvent event;
  int count;

  for (count = 0; count < numItems; count ++)
    {
      component = items[count].itemComponent;
      if (!component)
	  continue;

      // Any events pending?
      if (kernelWindowEventStreamRead(&(component->events), &event) > 0)
	{
	  kernelDebug(debug_gui, "Root menu item got event");
	  if (items[count].eventHandler)
	      items[count].eventHandler(component, &event);
	}
    }
}


static kernelWindow *makeShellWindow(void)
{
  // Make a main root window to serve as the background for the window
  // environment

  int status = 0;
  variableList settings;
  menuItem *tmpMenuItems = NULL;
  menuItem *tmpIconItems = NULL;
  char *iconName = NULL;
  char *menuName = NULL;
  char *itemName = NULL;
  char propertyName[128];
  char propertyValue[128];
  char itemLabel[128];
  char menuLabel[128];
  fileInfo tmpFile;
  image tmpImage;
  kernelWindowComponent *iconComponent = NULL;
  kernelWindowComponent *menuComponent = NULL;
  componentParameters params;
  int count1, count2;

  // We get default colors from here
  extern color kernelDefaultDesktop;

  // Get a new window
  rootWindow = kernelWindowNew(kernelMultitaskerGetCurrentProcessId(), WINNAME_ROOTWINDOW);
  if (rootWindow == NULL)
    return (rootWindow);

  // The window will have no border, title bar or close button, is not
  // movable or resizable, and is packed
  kernelWindowSetMovable(rootWindow, 0);
  kernelWindowSetResizable(rootWindow, 0);
  kernelWindowSetHasTitleBar(rootWindow, 0);
  kernelWindowSetHasBorder(rootWindow, 0);
  kernelWindowSetSkipTaskbar(rootWindow, 1);

  // Location in the top corner
  status = kernelWindowSetLocation(rootWindow, 0, 0);
  if (status < 0)
    return (rootWindow = NULL);

  // Size is the whole screen
  status = kernelWindowSetSize(rootWindow, kernelGraphicGetScreenWidth(),
			       kernelGraphicGetScreenHeight());
  if (status < 0)
    return (rootWindow = NULL);

  // The window is always at the bottom level
  kernelWindowSetLayer(rootWindow, 1);

  // Set our background color preference
  kernelWindowSetColors(rootWindow, &kernelDefaultDesktop);

  // Read the config file
  status = kernelConfigRead(WINDOW_DEFAULT_DESKTOP_CONFIG, &settings);
  if (status < 0)
    {
      // Argh.  No file?  Create a reasonable, empty list for us to use
      status = kernelVariableListCreate(&settings);
      if (status < 0)
	return (rootWindow = NULL);
    }

  // Try to load the background image
  if (!kernelVariableListGet(&settings, "background.image", propertyValue, 128) && strncmp(propertyValue, "", 128))
    {
      kernelDebug(debug_gui, "Loading background image \"%s\"", propertyValue);
      status = kernelImageLoad(propertyValue, 0, 0, &tmpImage);

      if (status == 0)
	{
	  // Put the background image into our window.
	  kernelWindowSetBackgroundImage(rootWindow, &tmpImage);
	  kernelWindowSetBackgroundImageMode(rootWindow, backgroundImageTiled);
	  kernelLog("Background image loaded");
	}
      else
	kernelError("Error loading background image %s",
		    propertyValue);

      if (tmpImage.data)
	{
	  // Release the image memory
	  kernelMemoryRelease(tmpImage.data);
	  tmpImage.data = NULL;
	}
    }

  // Make a task menu at the top
  kernelMemClear(&params, sizeof(componentParameters));
  params.foreground.red = 255;
  params.foreground.green = 255;
  params.foreground.blue = 255;
  params.background.red = kernelDefaultDesktop.red;
  params.background.green = kernelDefaultDesktop.green;
  params.background.blue = kernelDefaultDesktop.blue;
  params.flags |=
    (WINDOW_COMPFLAG_CUSTOMFOREGROUND | WINDOW_COMPFLAG_CUSTOMBACKGROUND);
  params.font = windowVariables->font.varWidth.medium.font;
  taskMenuBar = kernelWindowNewMenuBar(rootWindow, &params);

  // Try to load taskbar menus and menu items

  // Allocate temporary memory for the normal menu items
  tmpMenuItems = kernelMalloc(256 * sizeof(menuItem)); 
  if (tmpMenuItems == NULL)
    {
      kernelVariableListDestroy(&settings);
      return (rootWindow = NULL);
    }

  // Loop for variables with "taskBar.menu.*"
  for (count1 = 0; count1 < settings.numVariables; count1 ++)
    {
      if (!strncmp(settings.variables[count1], "taskBar.menu.", 13))
	{
	  menuName = (settings.variables[count1] + 13);
	  kernelVariableListGet(&settings, settings.variables[count1],
				menuLabel, 128);

	  menuComponent =
	    kernelWindowNewMenu(taskMenuBar, menuLabel, NULL, &params);

      kernelWindowComponentSetNotifiedProcess(menuComponent, kernelMultitaskerGetCurrentProcessId());

	  // Now loop and get any components for this menu
	  for (count2 = 0; count2 < settings.numVariables; count2 ++)
	    {
	      sprintf(propertyName, "taskBar.%s.item.", menuName);

	      if (!strncmp(settings.variables[count2], propertyName,
			   strlen(propertyName)))
		{
		  itemName =
		    (settings.variables[count2] + strlen(propertyName));
		  status = kernelVariableListGet(&settings,
						 settings.variables[count2],
						 itemLabel, 128);
		  if (status < 0)
		    continue;

		  // See if there's an associated command
		  sprintf(propertyName, "taskBar.%s.%s.command", menuName,
			  itemName);
		  status = kernelVariableListGet(&settings, propertyName,
						 propertyValue, 128);
		  if ((status < 0) ||
		      (kernelLoaderCheckCommand(propertyValue) < 0))
		    // No such command.  Don't show this one.
		    continue;

		  tmpMenuItems[numMenuItems].itemComponent =
		    kernelWindowNewMenuItem(menuComponent, itemLabel, &params);

		  strncpy(tmpMenuItems[numMenuItems].command, propertyValue,
			  MAX_PATH_NAME_LENGTH);

                  tmpMenuItems[numMenuItems].eventHandler = menuEvent;

		  numMenuItems += 1;
		}
	    }

	  // We treat any "window" menu specially, since it is not usually
	  // populated at startup time, only as windows are created or
	  // destroyed.
	  if (!strcmp(menuName, "window"))
	    {
	      kernelDebug(debug_gui, "Created window menu");
	      windowMenu = menuComponent;
	    }
	}
    }

  // Free our temporary menu item memory and just allocate the amount we
  // actually need.
  menuItems = kernelMalloc(numMenuItems * sizeof(menuItem)); 
  if (menuItems == NULL)
    {
      kernelVariableListDestroy(&settings);
      return (rootWindow = NULL);
    }
  kernelMemCopy(tmpMenuItems, menuItems, (numMenuItems * sizeof(menuItem)));
  kernelFree(tmpMenuItems);

  kernelLog("Task menu initialized");

  // Try to load icons

  // These parameters are the same for all icons
  kernelMemClear(&params, sizeof(componentParameters));
  params.gridWidth = 1;
  params.gridHeight = 1;
  params.padLeft = 5;
  params.padRight = 5;
  params.padTop = 5;
  params.padBottom = 0;
  params.flags =
    (WINDOW_COMPFLAG_CUSTOMFOREGROUND | WINDOW_COMPFLAG_CUSTOMBACKGROUND);
  kernelMemCopy(&COLOR_WHITE, &params.foreground, sizeof(color));
  kernelMemCopy(&kernelDefaultDesktop, &params.background,
		sizeof(color));
  params.orientationX = orient_center;
  params.orientationY = orient_middle;

  // Allocate temporary memory for the icon items
  tmpIconItems = kernelMalloc(256 * sizeof(menuItem)); 
  if (tmpIconItems == NULL)
    {
      kernelVariableListDestroy(&settings);
      return (rootWindow = NULL);
    }

  // Loop for variables with "icon.name.*"
  for (count1 = 0; count1 < settings.numVariables; count1 ++)
    {
      if (!strncmp(settings.variables[count1], "icon.name.", 10))
	{
	  iconName = (settings.variables[count1] + 10);
	  kernelVariableListGet(&settings, settings.variables[count1],
				itemLabel, 128);

	  // Get the rest of the recognized properties for this icon.

	  // Get the image name, make sure it exists, and try to load it.
	  sprintf(propertyName, "icon.%s.image", iconName);
	  status =
	    kernelVariableListGet(&settings, propertyName, propertyValue, 128);
	  if ((status < 0) ||
	      (kernelFileFind(propertyValue, &tmpFile) < 0) ||
	      (kernelImageLoad(propertyValue, 0, 0, &tmpImage) < 0))
	    continue;

	  // See if there's a command associated with this, and make sure
	  // the program exists.
	  sprintf(propertyName, "icon.%s.command", iconName);
	  status =
	    kernelVariableListGet(&settings, propertyName, propertyValue, 128);
	  if ((status < 0) || (kernelLoaderCheckCommand(propertyValue) < 0))
	    continue;

	  params.gridY++;

	  iconComponent =
	    kernelWindowNewIcon(rootWindow, &tmpImage, itemLabel, &params);
	  if (iconComponent == NULL)
	    continue;

	  kernelWindowComponentSetNotifiedProcess(iconComponent, kernelMultitaskerGetCurrentProcessId());

	  tmpIconItems[numIconItems].itemComponent = iconComponent;
	  strncpy(tmpIconItems[numIconItems].command, propertyValue,
			  MAX_PATH_NAME_LENGTH);
          tmpIconItems[numIconItems].eventHandler = iconEvent;
	  numIconItems += 1;

	  // Release the image memory
	  kernelMemoryRelease(tmpImage.data);
	  tmpImage.data = NULL;
	}
    }

  // Free our temporary icon item memory and just allocate the amount we
  // actually need.
  iconItems = kernelMalloc(numIconItems * sizeof(menuItem)); 
  if (iconItems == NULL)
    {
      kernelVariableListDestroy(&settings);
      return (rootWindow = NULL);
    }
  kernelMemCopy(tmpIconItems, iconItems, (numIconItems * sizeof(menuItem)));
  kernelFree(tmpIconItems);


  // Snap the icons to a grid
  kernelWindowSnapIcons((objectKey) rootWindow);
/*
  kernelDebug(debug_gui, "Rootwindow main container size %dx%d",
	      rootWindow->mainContainer->width,
	      rootWindow->mainContainer->height);
*/
  kernelLog("Desktop icons loaded");

  kernelDisk *configDisk = kernelDiskGetByPath(WINDOW_DEFAULT_DESKTOP_CONFIG);
  if (configDisk && !configDisk->filesystem.readOnly)
    {
      // Re-write the config file
      status =
	kernelConfigWrite(WINDOW_DEFAULT_DESKTOP_CONFIG, &settings);
      if (status >= 0)
	kernelLog("Updated desktop configuration");
    }

  kernelVariableListDestroy(&settings);

  initialized = 1;

  // Done.  We don't set it visible for now.
  return (rootWindow);
}


__attribute__((noreturn))
static void windowShellThread(void)
{
  // This thread runs as the 'window shell' to watch for window events on
  // 'root window' GUI components, and which functions as the user's login
  // shell in graphics mode.

  makeShellWindow();
  kernelWindowSetVisible(rootWindow, 1);

  kernelMultitaskerYield();

  if (login)
    runPrograms();

  while(1)
    {
      if (winMenuItems)
	scanMenuItemEvents(winMenuItems, numWinMenuItems);

      if (menuItems)
	scanMenuItemEvents(menuItems, numMenuItems);

      if (iconItems)
	scanMenuItemEvents(iconItems, numIconItems);

      // Done
//      kernelMultitaskerYield();

		int buffer;
		kernelMultitaskerWaitEvent(&buffer, sizeof(buffer), NULL, EVENT_FLAG_CUT);
    }
}


/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
//
// Below here, the functions are exported for external use
//
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////


int kernelWindowShell(int priv, int _login)
{
  // Launch the window shell thread

  privilege = priv;
  login = _login;

  // Spawn the window shell thread
  return(kernelMultitaskerSpawn(windowShellThread, "window shell", 0, NULL));
}

#include "kernelShutdown.h"

void kernelWindowShellUpdateList()
{
	// When the list of open windows has changed, the window environment can
	// call this function so we can update our taskbar.

	componentParameters params;
	kernelWindowComponent *item = NULL;
	int count;

	// Check initialization
	if (!initialized)
		return;

	kernelDebug(debug_gui, "Update window list");

	if (!windowMenu)
		return;

	// Destroy all the menu items in the menu
	int _numWinMenuItems = numWinMenuItems;
	numWinMenuItems = 0;
	for (count = 0; count < _numWinMenuItems; count++)
	{
		kernelWindowComponentDestroy(winMenuItems[count].itemComponent);
	}
	if (winMenuItems)
	{
		kernelFree(winMenuItems);
		winMenuItems = NULL;
	}

	// Copy the parameters from the menu to use 
	kernelMemCopy((void *) &(windowMenu->params), &params, sizeof(componentParameters));

	objectKey* windowKeys = NULL;
	int windowKeysSize = 0;
	int windowKeysNr = 0;
	while (1)
	{
		windowKeysNr = kernelWindowEnumerateWindows(windowKeys, windowKeysSize);
		if (windowKeysNr <= windowKeysSize)
			break;
		if (windowKeys)
			kernelFree(windowKeys);
		windowKeysSize = windowKeysNr;
		windowKeys = (objectKey*) kernelMalloc(sizeof(objectKey) * windowKeysSize);
		if (!windowKeys)
			goto out;
	}

	winMenuItems = kernelMalloc(windowKeysNr * sizeof(menuItem));
	if (!winMenuItems)
		goto out;

	char* title = NULL;
	int titleSize = 0;

	for (count = 0; count < windowKeysNr; count ++)
	{
		objectKey window = windowKeys[count];

		if (kernelWindowGetSkipTaskbar(window) != 0)
			continue;

		int titleLen = 0;
		while (1)
		{
			titleLen = kernelWindowGetTitle(window, title, titleSize);
			if (titleLen < titleSize - 1)
				break;
			if (title)
				kernelFree(title);
			titleSize = titleLen + 2;
			title = kernelMalloc(titleSize);
			if (!title)
				goto out;
		}
		item = kernelWindowNewMenuItem(windowMenu, title, &params);

		kernelWindowComponentSetNotifiedProcess(item, kernelMultitaskerGetCurrentProcessId());

		winMenuItems[numWinMenuItems].eventHandler = windowMenuEvent;
		winMenuItems[numWinMenuItems].itemComponent = item;
		winMenuItems[numWinMenuItems].window = window;
		numWinMenuItems += 1;
	}

	if (windowMenu->layout)
		windowMenu->layout(windowMenu);

	out:

	if (title)
		kernelFree(title);
	if (windowKeys)
		kernelFree(windowKeys);

}
